tree grammar SFigEval;

options {
    tokenVocab=SFig;
    ASTLabelType=CommonTree;
}

@header {
package org.vossnet.sfig;

import java.util.Properties;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import static java.lang.String.format;

import org.vossnet.sfig.RunFig;

import static org.vossnet.sfig.RunFig.*;
import static org.vossnet.sfig.StringUtil.*;
import static org.vossnet.sfig.BeanDef.BeanCycleAttrb;
}

@members {
private final Logger log = Logger.getLogger(getClass());
protected Properties properties = null;
public void setProperties(Properties properties) { this.properties = properties; }
protected Map<String,Object> instances = null;
public void setInstances(Map<String,Object> instances) { this.instances = instances; }
protected Map.Entry<String,Object> preDefObj = null;
public Map.Entry<String,Object> getPreDefObj() { return this.preDefObj; }
protected Map<String,BeanDef> beanDefsMap = null;
public void setBeanDefs(Map<String,BeanDef> beanDefsMap) { this.beanDefsMap = beanDefsMap; }
}

prog
    :   {if (instances == null) instances = new HashMap<String,Object>();}
        beanDef+
    ;


beanDef returns [Object obj]
scope {
    BeanDef bean_def;
}
    :   ^(BEAN_DEF b=beanOp) {$obj = $b.obj; $beanDef::bean_def.setAttributesSet(true);}
    ;

beanOp returns [Object obj]
    :   newStr   {$obj = $newStr.text;}
    |   newProps {$obj = $newProps.props;}
    |   newList  {$obj = $newList.list;}
    |   newBean  {$obj = $newBean.obj;}
    ;


processPreDef
    :   {instances = new HashMap<String,Object>();}
        b=beanDef
        {
            for(final Map.Entry<String,Object> entry : instances.entrySet()) {
                if (entry.getValue() == $b.obj) {
                    this.preDefObj = entry;
                    break;
                }
            }
        }
    ;


newStr returns [String text]
    :   ^(NEW_STR v=ID s=strDecl[$v.text]) {$text = $s.text;}
    ;

strDecl[String key] returns [String text]
    :   {$beanDef::bean_def = beanDefsMap.get($key);}
        s=strValue[$key] {$text = (String) $s.value;} 
    ;

strValue[String key] returns [Object value]
    :   v=strVal  {instances.put($key,$value = $v.value);}
    |   v=eStrVal {instances.put($key,$value = $v.value);}
    ;


newProps returns [Properties props]
    :   ^(NEW_PROPERTIES v=ID? c=PROPERTIES
        p=propsBody[$v != null ? $v.text : null, $c.text])
        {$props = $p.props;}
    ;

propsBody[String key,String clsName] returns [Properties props]
    :   {$beanDef::bean_def = beanDefsMap.get($key);}
        attribs* ctor?
        {
            $props = newInstance($clsName,$ctor.args,Properties.class);
            if ($key != null )
                instances.put($key,$props);
        }
        setProp[$props]*
    ;

attribs
    :   ^(SET_ATTRB n=attrbNm v=xexpr)
        {
            if (!$beanDef::bean_def.areAttributesSet()) {
            	final BeanCycleAttrb beanCycleAttrb = BeanCycleAttrb.get($n.text);
                if (beanCycleAttrb != null) {
                    beanCycleAttrb.execute($beanDef::bean_def,$v.value);
                    log.debug(format("\%nSet bean definition attribute: \"\%s\" = \"\%s\"\%n\%s\%n",
                                    $n.text,$v.value,$beanDef::bean_def));
                } else {
                    log.error(format("\"@\%s = \%s;\" references an unknown attribute on bean \'\%s\'",
                                    $n.text,$v.value,$beanDef::bean_def.beanID));
                }
            }
        }
    ;

attrbNm returns [String text]
    : id=(ID|IDB) {$text = $id.text;}
    ;

ctor returns [List<Object> args]
    :   ^(USE_CTOR list) {$args = $list.list;}
    ;

xexpr returns [Object value]
    :   ID      {$value = $ID.text;}
    |   expr    {$value = $expr.value;}
    ;

setProp[Properties props]
    :   ^(SET_PROP id=propID v=expr)
        {setPopKeyValue($props,$id.text,$v.value);}
    ;

propID returns [String text]
    :   ID      {$text = $ID.text;}
    |   strVal  {$text = (String) $strVal.value;}
    ;

expr returns [Object value]
    :   strVal  {$value = $strVal.value;}
    |   eStrVal {$value = $eStrVal.value;}
    |   INT     {$value = $INT.text;}
    |   FLT     {$value = $FLT.text;}
    |   CHR     {$value = $CHR.text;}
    |   BOOL    {$value = Boolean.valueOf($BOOL.text);}
    |   beanRef {$value = $beanRef.value;}
    |   list    {$value = $list.list;}
    ;

beanRef returns [Object value]
    :   ^(BEAN_REF ID)
        {
            if (instances.containsKey($ID.text)) {
                $value = instances.get($ID.text);
            } else {
                $value = getBean(this,beanDefsMap.get($ID.text));
                if ($value == null) {
                    $value = format("(BEAN_REF \%s)",$ID.text);
                    log.error("Invalid bean reference: " + $value.toString());
                }
            }
        }
    ;

strVal returns [Object value]
    :   ^(STR_VAL s=(STRING|MLTEXT)) {$value = preprocessString($s.text,properties);}
    ;

eStrVal returns [Object value]
    :   ^(ESC_STR_VAL s=(STRING|MLTEXT)) {$value = preprocessString($s.text,properties,false);}
    ;

list returns [List<Object> list]
    :   {$list = new ArrayList<Object>();}
        ^(ARRAY_LIST (expr {$list.add($expr.value);})*)
    ;


newList returns [List<Object> list]
    :   ^(NEW_LIST v=ID? c=LIST
        l=listBody[$v != null ? $v.text : null, $c.text])
        {$list = $l.list;}
    ;

listBody[String key,String clsName] returns [List<Object> list]
    :   {$beanDef::bean_def = beanDefsMap.get($key);}
        attribs* ctor?
        {
            $list = newInstance($clsName,$ctor.args,List.class);
            if ($key != null )
                instances.put($key,$list);
        }
        addListItem[$list]*
    ;

addListItem[List<Object> list]
    :   expr {$list.add($expr.value);}
    ;


newBean returns [Object obj]
    :   ^(NEW_BEAN ID c=QID
        b=beanBody[$ID.text,$c.text])
        {$obj = $b.obj;}
    ;

beanBody[String key,String clsName] returns [Object obj]
    :   {$beanDef::bean_def = beanDefsMap.get($key);}
        attribs* ctor?
        {
            $obj = newInstance($clsName,$ctor.args,Object.class);
            if ($key != null )
                instances.put($key,$obj);
        }
        setBeanProp[$obj]*
    ;

setBeanProp[Object obj]
    :   ^(SET_PROP ID expr)
        {setObjectProperty($obj,$ID.text,$expr.value);}
    ;
